<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
    </style>
</head>

<body>
    <canvas id="canvas"></canvas>
</body>
<script id="ColorVertexShader" type="x-shader/x-vertex">
    attribute vec4 a_Position;
    uniform float u_PointSize;
    uniform mat4 u_ModelM4;
    uniform mat4 u_PvM4;
    void main(){
        gl_Position = u_PvM4 * u_ModelM4 * a_Position;
        gl_PointSize = u_PointSize;
    }
</script>
<script id="ColorFragmentShader" type="x-shader/x-fragment">
    precision mediump float;
    uniform vec4 u_Color;
    void main(){
        gl_FragColor = u_Color;
    }
</script>
<script type="module">
    import { Matrix4, Vector3, PerspectiveCamera } from '../js/three.module.js';
    import { CreateProgram } from '../js/Utils.js';
    import CameraController from '../js/CameraController.js'

    import Geo from '../js/Poly2/Geo.js'
    import Mat from '../js/Poly2/Mat.js'
    import Obj3D from '../js/Poly2/Obj3D.js'
    import Scene from '../js/Poly2/Scene.js'



    //#region gl
    const canvas = document.getElementById("canvas");
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight - 4;

    const gl = canvas.getContext("webgl", { antialias: true, });

    gl.enable(gl.DEPTH_TEST);//开始背景遮挡会频闪
    // gl.enable(gl.BLEND);
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
    gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);//去图片描边
    //#endregion


    //#region  配置
    const radius = 0.4;//每层半径
    const elementCount = 10;//生成层数
    const interval = 0.07;//层之间的间隔
    const maxScale = 1.2;//放大倍数
    const scaleTime = 200;//放大动画时间
    //#endregion


    const pvM4 = new Matrix4();
    const modelM4 = new Matrix4();
    const tranModelM4 = new Matrix4();
    const eye = new Vector3(-1, 1, 3);
    const lookAt = new Vector3(0, 0, 0);

    const animModelM4 = new Matrix4();

    const scene = new Scene({ gl: gl });

    scene.regProgram(
        'color', {
        program: CreateProgram(gl,
            document.querySelector('#ColorVertexShader').innerText,
            document.querySelector('#ColorFragmentShader').innerText),
        attribs: ['a_Position'],
        uniforms: ['u_PvM4', 'u_ModelM4', 'u_Color', 'u_PointSize'],
    });

    const camera = new PerspectiveCamera(45, canvas.width / canvas.height, 1, 20);
    const cameraController = new CameraController({
        canvas: canvas,
        camera: camera,
        pvMatrix: pvM4,
        eye: eye,
        lookAt: lookAt,
    })

    let curSelectObj = null;//当前选择的对象

    const nodes = [
        -radius, 0, -radius,
        -radius, 0, radius,
        radius, 0, radius,
        radius, 0, -radius,
    ];
    const elements = [
        0, 1, 2,
        0, 2, 3,
    ];

    const faceArr = [];

    const mi = new Matrix4();
    const mb = new Matrix4();

    const animeArr = [];//动画队列
    const defAnime = {
        id: 1,//图形下标

        from: 0,
        to: 1,
        time: 1000,
        action: null,

        state: 0,//0:准备中 1:执行中 2:执行完毕
        beginTime: 0,
    }



    // Main
    {

        // scene.addRange(new Obj3D().createAxis('color'));

        createElements(elementCount);
        scene.allTransform();

    }

    //#region 创建辅助元素
    function createNode(vec3, color = [1, 1, 1, 1]) {
        return new Obj3D({
            geo: new Geo({
                data: {
                    'a_Position': { size: 3, array: new Float32Array([vec3.x, vec3.y, vec3.z]) },
                }
            }),
            mat: new Mat({
                program: 'color',
                mode: ['POINTS'],
                data: {
                    'u_PvM4': { type: 'uniformMatrix4fv', value: pvM4.elements },
                    'u_ModelM4': { type: 'uniformMatrix4fv', value: new Matrix4().elements },
                    'u_PointSize': { type: 'uniform1f', value: 10 },
                    'u_Color': { type: 'uniform4fv', value: color },
                },
            }),
        });
    }

    function createLine(a, b, color = [1, 0, 0, 1]) {
        return new Obj3D({
            geo: new Geo({
                data: {
                    'a_Position': { size: 3, array: new Float32Array([a.x, a.y, a.z, b.x, b.y, b.z]) },
                },
                element: { array: new Uint16Array([0, 1]) }
            }),
            mat: new Mat({
                program: 'color',
                mode: ['LINES', 'POINTS'],
                data: {
                    'u_PvM4': { type: 'uniformMatrix4fv', value: pvM4.elements },
                    'u_ModelM4': { type: 'uniformMatrix4fv', value: new Matrix4().elements },
                    'u_PointSize': { type: 'uniform1f', value: 10 },
                    'u_Color': { type: 'uniform4fv', value: color },
                },
            }),
        });
    }
    //#endregion



    //#region Event
    canvas.addEventListener('mousemove', function (event) {
        const { clientX, clientY } = event;
        //获取鼠标的世界坐标
        let curPosi = getViewToWorld(clientX, clientY);

        //判断是否鼠标是否在三角形内
        const obj = inTriangles(curPosi);//要如何判断深度信息呢,这样遍历查找是没有深度信息绘制的,即最先绘制的先选择
        if (obj != null) {
            //不为空:1.之前没有,现在有 2.之前有,现在还有
            if (curSelectObj == null) {
                //记录颜色历史,更改新颜色
                selectObj(obj);
            } else {
                //如果不是同一个
                if (obj != curSelectObj.obj) {
                    //上一个返回颜色
                    unSelectObj();
                    //渲染新的颜色
                    selectObj(obj);
                }
            }
        } else {
            //为空:1.一直没有 2.之前有,现在没了
            if (curSelectObj != null) {
                unSelectObj();
                curSelectObj = null;
            }
        }

    });

    function unSelectObj() {
        const { obj, oldModelM4, orgin } = curSelectObj;

        addAnime({
            id: obj.id,
            time: scaleTime,
            from: obj.mat.data['u_ModelM4'].value[0],
            to: 1,
            data: {
                orgin: orgin,
                obj: obj,
            },
            action: (num, data) => {
                const { orgin, obj } = data;
                obj.mat.data['u_ModelM4'].value = getModelM4(orgin, num).elements;
                //总觉得这地方不是很对, 不应该直接覆盖modelM4, 之后回来再看
            }
        })
    }

    function selectObj(newObj) {

        let array = newObj.geo.data['a_Position'].array;
        let center = getCenter(array);
        //基点
        let orgin = getOrginOfEyeInvert(eye, center, array);

        addAnime({
            id: newObj.id,
            time: scaleTime,
            from: newObj.mat.data['u_ModelM4'].value[0],
            to: maxScale,
            data: {
                orgin: orgin,
                obj: newObj,
            },
            action: (num, data) => {
                const { orgin, obj } = data;
                obj.mat.data['u_ModelM4'].value = getModelM4(orgin, num).elements;
                //总觉得这地方不是很对, 不应该直接覆盖modelM4, 之后回来再看
            }
        });

        curSelectObj = {
            obj: newObj,
            orgin: orgin,
        };

    }

    function getModelM4(orgin, scale) {
        mi.setPosition(-orgin.x, -orgin.y, -orgin.z);
        mb.setPosition(orgin.x, orgin.y, orgin.z);
        return mb.clone()
            .multiply(new Matrix4().makeScale(scale, scale, scale))
            .multiply(mi);
    }

    //#endregion

    //#region 获取基点
    //获取视线反方向的图形内最大基点
    function getOrginOfEyeInvert(E, C, array) {
        //方向, 
        const V = C.clone().sub(E).normalize();
        //方向上的点(这里一定要加上原点啊啊啊啊,被0 0 0 的原点给迷惑了)
        const Vnode = V.clone().add(C);

        //去y轴 (为什么要去y,因为是平行的吗)(完全没有头绪啊啊啊啊,先搁置吧,完全垂直的平面无法计算)
        Vnode.y = C.y;

        //获取方向最近的两个点
        let [near0, near1] = getNearNode(Vnode, array, 2);
        let [x, y] = [0, 0];

        if (V.x == 0) {
            x = 0;
            y = near0.z - C.z;
        } else if (V.z == 0) {
            x = near0.x - C.x;
            y = 0;
        } else if (Math.abs(V.x) < Math.abs(V.z)) {
            //获取视线的三角形
            let [d, l] = [V.x, V.z];
            //获取角度
            let J = d / l;
            y = near0.z - C.z;
            x = y * J;
        } else {
            let [d, l] = [V.z, V.x];
            //获取角度
            let J = d / l;
            if (l == 0) {
                J = 0;
            }
            x = near0.x - C.x;
            y = x * J;
        }

        let orgin = new Vector3(x, 0, y).add(C);
        //开始算y
        //lb斜边长
        let lb = near0.clone().distanceTo(new Vector3(near1.x, near0.y, near1.z));
        orgin.y = near0.y;
        //所求的边长
        let lb2 = orgin.clone().distanceTo(near0);
        //方向
        let nfx = near1.clone().sub(near0).normalize();
        //方向 * 距离
        let o2 = nfx.clone().multiplyScalar(lb2 / lb).add(near0);
        return o2;
    }
    //获取图形中心点
    function getCenter(array) {
        let a = new Vector3(array[0], array[1], array[2]);
        let b = new Vector3(array[3 * 2], array[3 * 2 + 1], array[3 * 2 + 2]);
        return b.clone().sub(a).multiplyScalar(0.5).add(a);
    }
    //获取最近的点
    function getNearNode(node, array, count = 1) {
        let cds = [];
        for (let i = 0; i < array.length; i += 3) {
            let cur = new Vector3(array[i], array[i + 1], array[i + 2]);
            cds.push({ value: cur.distanceTo(node), index: i })
        }
        let rIndex = [];
        for (let i = 0; i < count; i++) {
            let min = cds[0];
            for (let j = 1; j < cds.length; j++) {
                if (min.value > cds[j].value) {
                    min = cds[j];
                }
            }

            rIndex.push(new Vector3(array[min.index], array[min.index + 1], array[min.index + 2]));
            cds.splice(min.index / 3, 1);
        }
        return rIndex;
    }
    //#endregion

    //#region 选择对象
    //获取鼠标的世界矩阵
    function getViewToWorld(clientX, clientY) {
        const [hx, hy] = [canvas.width / 2, canvas.height / 2];
        const [x, y] = [(clientX - hx) / hx, -(clientY - hy) / hy];
        const worldPosi = new Vector3(x, y, 0).applyMatrix4(pvM4.clone().invert())
        return worldPosi;
    }

    //判断是否在三角形中
    function inTriangles(posi) {
        let inTriangleObj = null;
        for (let obj of faceArr) {
            let triangles = getTriangle(obj);

            //遍历每个三角形是否在里面
            for (let triangle of triangles) {
                //
                const m = inTriangle(posi, triangle.a, triangle.b, triangle.c);
                if (m != null) {
                    inTriangleObj = obj;
                    break;
                }
            }

            if (inTriangleObj != null) {
                break;
            }
        }
        return inTriangleObj;
    }
    function inTriangle(v, A, B, C) {
        //视点E
        const E = eye;
        //平面法向F
        const F = B.clone().sub(A).cross(C.clone().sub(B));
        //方向V
        const V = v.clone().sub(E).normalize();

        // (M - A) · F = 0;
        // (M - E) = V * 距离
        // M = E + (V * 距离)
        // ((E + (V * 距离) - A) · F = 0

        // (E - A + V距离) · F = 0
        // (E - A) · F + (V距离 · F) = 0
        // V距离 · F = -(E - A) · F
        // V距离 · F = (A - E) · F
        // 距离 = ((A - E) · F) / (V · F)
        // M = E + ((((A - E) · F) / (V · F)) * V)
        const M = V.clone().multiplyScalar(A.clone().sub(E).dot(F) / V.clone().dot(F)).add(E);


        //判断是否在三角形内
        const node = [A, B, C];
        for (let i = 0; i < 3; i++) {
            const a = node[i];
            const b = node[(i + 1) % 3];
            const MA = a.clone().sub(M);
            const AB = b.clone().sub(a);
            const MF = MA.clone().cross(AB);
            if (MF.dot(F) < 0) {
                //不在
                return null;
            }
        }
        return M;
    }

    //获取obj3D对象中的三角形
    function getTriangle(obj3D) {
        const element = obj3D.geo.element.array;
        const node = obj3D.geo.data['a_Position'].array;

        const triangles = [];
        const modelM4 = new Matrix4();
        modelM4.elements = obj3D.mat.data['u_ModelM4'].value;

        for (let i = 0; i < element.length; i += 3) {
            const a = new Vector3(
                node[element[i] * 3 + 0],
                node[element[i] * 3 + 1],
                node[element[i] * 3 + 2],
            ).applyMatrix4(modelM4);
            const b = new Vector3(
                node[element[i + 1] * 3 + 0],
                node[element[i + 1] * 3 + 1],
                node[element[i + 1] * 3 + 2],
            ).applyMatrix4(modelM4);
            const c = new Vector3(
                node[element[i + 2] * 3 + 0],
                node[element[i + 2] * 3 + 1],
                node[element[i + 2] * 3 + 2],
            ).applyMatrix4(modelM4);
            triangles.push({ a: a, b: b, c: c });
        }
        return triangles;
    }
    //#endregion

    //#region 创建元素
    function createElements(num = 10) {
        for (let i = num / 2; i > num / 2 - num; i--) {
            //获取当前层的y值
            let curNodes = getNodesOfY(i * interval);
            let elemenet = createElement(curNodes);
            elemenet.id = i;
            faceArr.push(elemenet);
        }

    }

    function createElement(nodes) {
        let obj = new Obj3D({
            geo: new Geo({
                data: {
                    'a_Position': { size: 3, array: new Float32Array(nodes) },
                },
                element: {
                    array: new Uint16Array(elements)
                }
            }),
            mat: new Mat({
                program: 'color',
                data: {
                    'u_PvM4': { type: 'uniformMatrix4fv', value: pvM4.elements },
                    'u_ModelM4': { type: 'uniformMatrix4fv', value: modelM4.elements },
                    'u_PointSize': { type: 'uniform1f', value: 100 },
                    'u_Color': { type: 'uniform4fv', value: getRandomColor() },
                },
                mode: ['TRIANGLES'],
            }),
        });
        scene.add(obj);
        return obj;
    }

    function getNodesOfY(y) {
        let curNodes = nodes.slice()
        for (let i = 0; i < nodes.length; i += 3) {
            curNodes[i + 1] = y;
        }
        return curNodes;
    }

    function getRandomColor() {
        const [r, g, b] = [Math.random(), Math.random(), Math.random()];
        return [r, g, b, 1];
    }
    //#endregion

    function draw() {


        scene.setUniform('u_PvM4', { value: pvM4.elements })
        scene.draw();
    }

    function addAnime(data) {
        //如果存在动画
        for (let i = 0; i < animeArr.length; i++) {
            if (animeArr[i].id == data.id) {
                animeArr[i].from = data.from;
                animeArr[i].to = data.to;
                animeArr[i].state = 0;
                return;
            }
        }
        data.state = 0;
        animeArr.push(data);
    }

    (function ani(time) {

        for (let i = 0; i < animeArr.length; i++) {
            let anime = animeArr[i];
            if (anime.state == 0) {
                anime.state = 1;
                anime.beginTime = time;
                // console.log(`动画[${anime.id}]开始执行`);
                // console.log('开始:' + animModelM4.elements[0])
            }

            if (anime.state == 1) {
                if (time > anime.beginTime + anime.time) {
                    anime.state = 2;
                } else {
                    //获取百分比
                    let a = (time - anime.beginTime) / anime.time;
                    let num = (anime.to - anime.from) * a + anime.from;
                    anime.action(num, anime.data);
                }
            } else {
                //删除
                animeArr.splice(i, 1);
                // console.log(`动画[${anime.id}]执行完毕`);
                //强制归零
                anime.action(anime.to, anime.data);
                // console.log('结束:' + animModelM4.elements[0])
                i--;
            }
        }

        draw();
        requestAnimationFrame(ani);
    })(0);
</script>

</html>